package org.awater.animation;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

import android.app.Instrumentation;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.widget.FrameLayout;

public class AnimationLayer extends FrameLayout implements IAnimationLayer {

	private Handler mHander;
	private transient Runnable mPostEvent;

	private transient int firstIndex;
	private transient int lastIndex;
	private transient int currentIndex;
	private transient int modCount;
	private transient AbstractAnimation[] array;

	public AnimationLayer(Context context) {
		super(context);
		init(10);
	}

	public AnimationLayer(Context context, AttributeSet attrs, int defStyle,
			int capacity) {
		super(context, attrs, defStyle);
		init(capacity);
	}

	public AnimationLayer(Context context, AttributeSet attrs, int capacity) {
		super(context, attrs);
		init(capacity);
	}

	private void init(int capacity) {
		mPostEvent = new Runnable() {
			Instrumentation mInstrumentation = new Instrumentation();

			@Override
			public void run() {
				mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_RIGHT);
			}
		};
		firstIndex = lastIndex = currentIndex = 0;
		array = newElementArray(capacity);
	}

	private AbstractAnimation[] newElementArray(int size) {
		return (AbstractAnimation[]) new Object[size];
	}

	@Override
	public void run() {
		Looper.loop();
		mHander = new Handler();
		Looper.loop();
	}

	@Override
	public void animate() {
		if (array[currentIndex].hasNext()) {
			array[currentIndex].renderFrame();
		} else {
			if (hasNext()) {
				currentIndex++;
			}
		}
		mHander.post(mPostEvent);
	}

	@Override
	public boolean add(AbstractAnimation object) {
		if (lastIndex == array.length) {
			growAtEnd(1);
		}
		array[lastIndex++] = object;
		modCount++;
		return true;
	}

	@Override
	public void add(int location, AbstractAnimation object) {
		int size = lastIndex - firstIndex;
		if (0 < location && location < size) {
			if (firstIndex == 0 && lastIndex == array.length) {
				growForInsert(location, 1);
			} else if ((location < size / 2 && firstIndex > 0)
					|| lastIndex == array.length) {
				System.arraycopy(array, firstIndex, array, --firstIndex,
						location);
			} else {
				int index = location + firstIndex;
				System.arraycopy(array, index, array, index + 1, size
						- location);
				lastIndex++;
			}
			array[location + firstIndex] = object;
		} else if (location == 0) {
			if (firstIndex == 0) {
				growAtFront(1);
			}
			array[--firstIndex] = object;
		} else if (location == size) {
			if (lastIndex == array.length) {
				growAtEnd(1);
			}
			array[lastIndex++] = object;
		} else {
			throw new IndexOutOfBoundsException();
			// luni.0A=Index: {0}, Size: {1}
			//Messages.getString("luni.0A", //$NON-NLS-1$
			// Integer.valueOf(location),
			// Integer.valueOf(lastIndex - firstIndex)));
		}

		modCount++;

	}

	@Override
	public boolean addAll(Collection<? extends AbstractAnimation> collection) {
		Object[] dumpArray = collection.toArray();
		if (dumpArray.length == 0) {
			return false;
		}
		if (dumpArray.length > array.length - lastIndex) {
			growAtEnd(dumpArray.length);
		}
		System.arraycopy(dumpArray, 0, this.array, lastIndex, dumpArray.length);
		lastIndex += dumpArray.length;
		modCount++;
		return true;
	}

	@Override
	public boolean addAll(int location,
			Collection<? extends AbstractAnimation> collection) {
		Iterator<? extends AbstractAnimation> it = collection.iterator();
		while (it.hasNext()) {
			add(location++, it.next());
		}
		return !collection.isEmpty();
	}

	@Override
	public void clear() {
		if (firstIndex != lastIndex) {
			Arrays.fill(array, firstIndex, lastIndex, null);
			firstIndex = lastIndex = 0;
			modCount++;
		}
	}

	@Override
	public boolean contains(Object object) {
		if (object != null) {
			for (int i = firstIndex; i < lastIndex; i++) {
				if (object.equals(array[i])) {
					return true;
				}
			}
		} else {
			for (int i = firstIndex; i < lastIndex; i++) {
				if (array[i] == null) {
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public boolean containsAll(Collection<?> collection) {
		Iterator<?> it = collection.iterator();
		while (it.hasNext()) {
			if (!contains(it.next())) {
				return false;
			}
		}
		return true;
	}

	@Override
	public AbstractAnimation get(int location) {
		if (0 <= location && location < (lastIndex - firstIndex)) {
			return array[firstIndex + location];
		}
		throw new IndexOutOfBoundsException();
		// luni.0A=Index: {0}, Size: {1}
		//Messages.getString("luni.0A", //$NON-NLS-1$
		// Integer.valueOf(location),
		// Integer.valueOf(lastIndex - firstIndex)));
	}

	@Override
	public int indexOf(Object object) {
		if (object != null) {
			for (int i = firstIndex; i < lastIndex; i++) {
				if (object.equals(array[i])) {
					return i - firstIndex;
				}
			}
		} else {
			for (int i = firstIndex; i < lastIndex; i++) {
				if (array[i] == null) {
					return i - firstIndex;
				}
			}
		}
		return -1;
	}

	@Override
	public boolean isEmpty() {
		return lastIndex == firstIndex;
	}

	@Override
	public Iterator<AbstractAnimation> iterator() {
		return null;
	}

	@Override
	public AbstractAnimation remove(int location) {
		AbstractAnimation result;
		int size = lastIndex - firstIndex;
		if (0 <= location && location < size) {
			if (location == size - 1) {
				result = array[--lastIndex];
				array[lastIndex] = null;
			} else if (location == 0) {
				result = array[firstIndex];
				array[firstIndex++] = null;
			} else {
				int elementIndex = firstIndex + location;
				result = array[elementIndex];
				if (location < size / 2) {
					System.arraycopy(array, firstIndex, array, firstIndex + 1,
							location);
					array[firstIndex++] = null;
				} else {
					System.arraycopy(array, elementIndex + 1, array,
							elementIndex, size - location - 1);
					array[--lastIndex] = null;
				}
			}
			if (firstIndex == lastIndex) {
				firstIndex = lastIndex = 0;
			}
		} else {
			throw new IndexOutOfBoundsException();
			// luni.0A=Index: {0}, Size: {1}
			//                    Messages.getString("luni.0A", //$NON-NLS-1$
			// Integer.valueOf(location),
			// Integer.valueOf(lastIndex - firstIndex)));
		}

		modCount++;
		return result;
	}

	@Override
	public boolean remove(Object object) {
		int location = indexOf(object);
		if (location >= 0) {
			remove(location);
			return true;
		}
		return false;
	}

	@Override
	public boolean removeAll(Collection<?> collection) {
		boolean result = false;
		Iterator<?> it = iterator();
		while (it.hasNext()) {
			if (collection.contains(it.next())) {
				it.remove();
				result = true;
			}
		}
		return result;
	}

	@Override
	public boolean retainAll(Collection<?> collection) {
		boolean result = false;
		Iterator<?> it = iterator();
		while (it.hasNext()) {
			if (!collection.contains(it.next())) {
				it.remove();
				result = true;
			}
		}
		return result;
	}

	@Override
	public AbstractAnimation set(int location, AbstractAnimation object) {
		if (0 <= location && location < (lastIndex - firstIndex)) {
			AbstractAnimation result = array[firstIndex + location];
			array[firstIndex + location] = object;
			return result;
		}
		throw new IndexOutOfBoundsException();
		// luni.0A=Index: {0}, Size: {1}
		//                Messages.getString("luni.0A", //$NON-NLS-1$
		// Integer.valueOf(location),
		// Integer.valueOf(lastIndex - firstIndex)));
	}

	@Override
	public int size() {
		return lastIndex - firstIndex;
	}

	@Override
	public Object[] toArray() {
		int size = lastIndex - firstIndex;
		Object[] result = new Object[size];
		System.arraycopy(array, firstIndex, result, 0, size);
		return result;
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T[] toArray(T[] contents) {
		int size = lastIndex - firstIndex;
		if (size > contents.length) {
			Class<?> ct = contents.getClass().getComponentType();
			contents = (T[]) Array.newInstance(ct, size);
		}
		System.arraycopy(array, firstIndex, contents, 0, size);
		if (size < contents.length) {
			contents[size] = null;
		}
		return contents;
	}

	private void growAtEnd(int required) {
		int size = lastIndex - firstIndex;
		if (firstIndex >= required - (array.length - lastIndex)) {
			int newLast = lastIndex - firstIndex;
			if (size > 0) {
				System.arraycopy(array, firstIndex, array, 0, size);
				int start = newLast < firstIndex ? firstIndex : newLast;
				Arrays.fill(array, start, array.length, null);
			}
			firstIndex = 0;
			lastIndex = newLast;
		} else {
			int increment = size / 2;
			if (required > increment) {
				increment = required;
			}
			if (increment < 12) {
				increment = 12;
			}
			AbstractAnimation[] newArray = newElementArray(size + increment);
			if (size > 0) {
				System.arraycopy(array, firstIndex, newArray, 0, size);
				firstIndex = 0;
				lastIndex = size;
			}
			array = newArray;
		}
	}

	private void growAtFront(int required) {
		int size = lastIndex - firstIndex;
		if (array.length - lastIndex + firstIndex >= required) {
			int newFirst = array.length - size;
			if (size > 0) {
				System.arraycopy(array, firstIndex, array, newFirst, size);
				int length = firstIndex + size > newFirst ? newFirst
						: firstIndex + size;
				Arrays.fill(array, firstIndex, length, null);
			}
			firstIndex = newFirst;
			lastIndex = array.length;
		} else {
			int increment = size / 2;
			if (required > increment) {
				increment = required;
			}
			if (increment < 12) {
				increment = 12;
			}
			AbstractAnimation[] newArray = newElementArray(size + increment);
			if (size > 0) {
				System.arraycopy(array, firstIndex, newArray, newArray.length
						- size, size);
			}
			firstIndex = newArray.length - size;
			lastIndex = newArray.length;
			array = newArray;
		}
	}

	private void growForInsert(int location, int required) {
		int size = lastIndex - firstIndex;
		int increment = size / 2;
		if (required > increment) {
			increment = required;
		}
		if (increment < 12) {
			increment = 12;
		}
		AbstractAnimation[] newArray = newElementArray(size + increment);
		int newFirst = increment - required;
		// Copy elements after location to the new array skipping inserted
		// elements
		System.arraycopy(array, location + firstIndex, newArray, newFirst
				+ location + required, size - location);
		// Copy elements before location to the new array from firstIndex
		System.arraycopy(array, firstIndex, newArray, newFirst, location);
		firstIndex = newFirst;
		lastIndex = size + increment;

		array = newArray;
	}

	@Override
	public boolean hasNext() {
		return currentIndex < (array.length - 1);
	}

	@Override
	public AbstractAnimation next() {
		return array[currentIndex++];
	}

	@Override
	public void remove() {
		remove(currentIndex);
	}
}
